// References: https://baijiahao.baidu.com/s?id=1735577033729027737
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static char __table[64];
static char __isFilled = 0;

static void __fill_part_table(char start, char end, int idx)
{
    int i;
    for (i = start; i <= end; i++)
    {
        __table[idx++] = i;
    }
}

static void __fill_table(void)
{
    if (__isFilled)
    {
        return;
    }
    __isFilled = 1;

    __fill_part_table('A', 'Z', 0);
    __fill_part_table('a', 'z', 26);
    __fill_part_table('0', '9', 52);
    __table[62] = '+';
    __table[63] = '/';
}

static int __get_idx_from_table(int ch)
{
    int idx;
    if ((ch >= 'A') && (ch <= 'Z'))
    {
        idx = ch - 'A';
    }
    else if ((ch >= 'a') && (ch <= 'z'))
    {
        idx = ch - 'a' + 26;
    }
    else if ((ch >= '0') && (ch <= '9'))
    {
        idx = ch - '0' + 52;
    }
    else if ('+' == ch)
    {
        idx = 62;
    }
    else if ('/' == ch)
    {
        idx = 63;
    }
    else if ('=' == ch)
    {
        idx = 65;
    }
    else
    {
        fprintf(stderr, "Unknow character: %c\n", ch);
        return -1;
    }
    return idx;
}

size_t base64Encode(const void* data, size_t dataLen, void **pout)
{
    size_t inIdx = 0, outIdx = 0, endPos, bufSize;
    const unsigned char *bufIn = (const unsigned char *)data;
    char *bufOut = NULL;
    char remainder = dataLen % 3;

    __fill_table();

    // 向上取整
    bufSize = ((dataLen + 2) / 3) * 4 + 1; // 每3字节编码为4字符，加上终止符
    // printf("string: %lld, buf: %lld - ", (long long) strlen(bufIn), (long long) bufSize);
    bufOut = (char *) calloc(1, bufSize);
    if (!bufOut)
    {
        fprintf(stderr, "No memory!\n");
        goto CleanUp;
    }

    if (dataLen >= 3)
    {
        endPos = remainder == 0 ? dataLen : dataLen - 3;
        for (inIdx = outIdx = 0; inIdx < endPos; inIdx+=3)
        {
            bufOut[outIdx++] = __table[(bufIn[inIdx] & 0xFC) >> 2];
            bufOut[outIdx++] = __table[((bufIn[inIdx] & 0x03) << 4) | ((bufIn[inIdx+1] & 0xF0) >> 4)];
            bufOut[outIdx++] = __table[((bufIn[inIdx+1] & 0x0F) << 2) | ((bufIn[inIdx+2] & 0xC0) >> 6)];
            bufOut[outIdx++] = __table[bufIn[inIdx+2] & 0x3F];
        }
    }

    if (remainder)
    {
        bufOut[outIdx++] = __table[(bufIn[inIdx] & 0xFC) >> 2];
        switch (remainder)
        {
            case 1:
            {
                bufOut[outIdx++] = __table[(bufIn[inIdx] & 0x03) << 4];
                bufOut[outIdx++] = '=';
                break;
            }
            case 2:
            {
                bufOut[outIdx++] = __table[((bufIn[inIdx] & 0x03) << 4) | ((bufIn[inIdx+1] & 0xF0) >> 4)];
                bufOut[outIdx++] = __table[(bufIn[inIdx+1] & 0x0F) << 2];
                break;
            }
            default:
            {
                fprintf(stderr, "Calculate remainder error, The remainder is %d\n", remainder);
                goto CleanUp;
            }
        }
        bufOut[outIdx++] = '=';
    }

    bufOut[outIdx] = 0;

    *pout = bufOut;

    return outIdx;

CleanUp:

    if (bufOut)
    {
        free(bufOut);
    }

    return -1;
}

size_t base64Decode(const void* base64, size_t base64Len, void **pout)
{
    size_t inIdx = 0, outIdx = 0, endPos;
    const char *bufIn = (const char *)base64;
    char *bufOut = NULL;

    if (base64Len % 4 != 0)
    {
        fprintf(stderr, "Base64 length error!\n");
        goto CleanUp;
    }

    bufOut = (char *) calloc(1, base64Len);
    if (!bufOut)
    {
        fprintf(stderr, "No memory!\n");
        goto CleanUp;
    }

    endPos = base64Len;
    if (('=' == bufIn[base64Len - 2]) || ('=' == bufIn[base64Len - 1]))
    {
        endPos = base64Len - 4;
    }

    for (inIdx = 0; inIdx < endPos; inIdx += 4)
    {
        bufOut[outIdx++] = (__get_idx_from_table(bufIn[inIdx]) << 2) \
            | ((__get_idx_from_table(bufIn[inIdx+1]) & 0x30) >> 4);

        bufOut[outIdx++] = ((__get_idx_from_table(bufIn[inIdx+1]) & 0x0F) << 4) \
            | ((__get_idx_from_table(bufIn[inIdx+2]) & 0x3C) >> 2);

        bufOut[outIdx++] = ((__get_idx_from_table(bufIn[inIdx+2]) & 0x03) << 6) \
            | (__get_idx_from_table(bufIn[inIdx+3]) & 0x3F);
    }

    if (endPos != base64Len)
    {
        bufOut[outIdx++] = (__get_idx_from_table(bufIn[inIdx]) << 2) \
            | ((__get_idx_from_table(bufIn[inIdx+1]) & 0x30) >> 4);

        if ('=' == bufIn[base64Len - 2])
        {
            goto Finish;
        }

        bufOut[outIdx++] = ((__get_idx_from_table(bufIn[inIdx+1]) & 0x0F) << 4) \
            | ((__get_idx_from_table(bufIn[inIdx+2]) & 0x3C) >> 2);
    }

Finish:

    bufOut[outIdx] = 0;

    *pout = bufOut;

    return outIdx;

CleanUp:

    if (bufOut)
    {
        free(bufOut);
    }

    return -1;
}

void base64Release(void *p)
{
    if (p)
    {
        free(p);
    }
}

#if 0

int main(int argc, char *argv[])
{
    int i;
    const char *in[] = {"you", "yo", "d", "youyou", "youyo", "youd"};
    char *out = NULL;
    char *dec = NULL;
    int inLen = sizeof(in) / sizeof(char*);
    size_t baseLen, decLen;

    for (i = 0; i < inLen; i++)
    {
        baseLen = base64Encode(in[i], strlen(in[i]), (void **) &out);
        decLen = base64Decode(out, baseLen, (void **) &dec);
        if ((baseLen < 0) || (decLen < 0))
        {
            return 1;
        }
        printf("[%d] %s --- [%d] %s --- [%d] %s\n", \
            (int)strlen(in[i]), in[i], \
            (int)baseLen, out, \
            (int)decLen, dec);
    }

    base64Release(out);
    base64Release(dec);

    return 0;
}

#endif
